/**
 * Copyright (c) 2009, Microsoft
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Microsoft nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Microsoft ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Microsoft BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @category   Microsoft
 * @package    WebErrorPHPToolkit
 * @subpackage Error
 * @version    $Id: bing_json.js, v 1.0 2009-10-09 08:00:00Z unknown $
 * @copyright  Copyright (c) 2009, Microsoft (http://www.microsoft.com)
 * @author     a-jetann
 *
 * This is the main body of JavaScript code for performing Bing query via
 * a JSON endpoint, with response callback.
 */
 
/**
 * Main function that performs Bing search based upon the search terms provided
 * either upon upload of Error.php file or from search box entry.
 *
 * @param query_param string search terms
 * @return void
 * @throws Exception if problem in putting together Bing search parameters.
 */
function searchBingJSON(query_param)
{
  try
  {
    var query = query_param.trim();
    
    if ( query.length == 0)
    {
      showNoResults("No search request provided!");
      return;
    }
    
    setValueByElementId("SearchTerms", query);
    var endpoint = getValidElementById("BingSearchJsonEndpoint").value;
    var varBingAppID = getValidElementById("BingAppID").value;
    var varBingApiVersion = getValidElementById("BingApiVersion").value;
    var varBingMarket = getValidElementById("BingMarket").value;
    var varBingSiteDomains = getValidElementById("BingSiteDomains").value;
    var varQuery = String.format("{0} {1}", query, varBingSiteDomains);
    
    var maxCount = getValidElementById("BingMaximumResultsCount").value;
    var varWebCount = (maxCount == 0) ? '' : maxCount + '';
    
    var varBingSpellCorrectionEnabled = getValidElementById("BingSpellCorrectionEnabled").value;
    var varWebOptions = varBingSpellCorrectionEnabled ? '' : "DisableQueryAlterations";
    
    var varSources = getSourcesList();
    
    /*
    * Artifact with EnableHighlighting with IE6 and IE7
    */
    var varOptions = ((typeof document.getElementsByClassName != 'undefined') || (typeof document.documentMode != 'undefined'))
    ? "EnableHighlighting"
    : "";
    
    var queryParameters =
    {
      "AppID": varBingAppID,
      "Version": varBingApiVersion,
      "Market": varBingMarket,
      "Web.Count": varWebCount,
      "Adult": "Strict", // query should not include sexually explicit text, images, or videos.
      "Options": varOptions,
      "JsonType": "callback",
      "JsonCallback": "searchBingCallback",
      "Query": varQuery,
      "Sources": varSources,
      "Web.Options": varWebOptions
    };
    
    executeSearchRequest(buildUrl(endpoint, queryParameters));
  }
  catch (ex)
  {
    debugAlert("searchBingJSON: Exception: "+ex.name+": "+ex.message);    
  }
}



/**
 * Gets the source types for the request. If BingSpellCorrectionEnabled is true then this will return Web+Spell,
 * otherwise only Web will be returned.
 *
 * @return string Requested sources in Bing response.
 */
function getSourcesList()
{
  var varSources = "Web";
  varSources = varSources+" "+((getValidElementById("BingSpellCorrectionEnabled").value) ? "Spell" : "");
  varSources = varSources+" "+((getValidElementById("BingSearchImageEnabled").value) ? "Image" : "");
  
  varSources = varSources.trim();
  var varSources_array = varSources.split(" ");
  if (varSources_array.length > 1)
  {
    return varSources_array.join("+");
  }
  
  return varSources;
}

/**
 * setTimeout() returns a numeric timeout ID that can be used to 
 * track the timeout. This is most commonly used with the clearTimeout() method. 
 */
var gTimeoutBingID = null;

/**
 * JavaScript code (called an expression) will be run a specified 
 * number of milliseconds from when the setTimeout() method was called.
 *
 * @return void
 */
function timeoutBingCallback() 
{
  showNoResults("No search response from Bing!");
}

/**
 * Sleep in milliseconds, simulating heavy processing and for misc performance measurements.
 * 
 * @param milliseconds integer
 * @return void
 */
function sleep(milliseconds) {
    var start = new Date().getTime();
    for (var i = 0; i < 1e7; i++) {
      if ((new Date().getTime() - start) > milliseconds){
        break;
      }
    }
  }
/**
 * Executes the searchBingJSON by loading the script into the SearchCallback script block.
 * 
 * @param url string Bing JSON endpoint URL with Bing search parameters in path.
 * @return void
 */
function executeSearchRequest(url)
{
  var script = document.createElement('SCRIPT');
  if (script)
  {
    if ( !isNull(gTimeoutBingID) ) 
    {
      clearTimeout( gTimeoutBingID );  
      gTimeoutBingID = null;
    }
    
    var secTimeout = parseInt(getBingConnectTimeout());
    if (secTimeout > 0)
    {
      var msecTimeout = secTimeout * 1000;
      gTimeoutBingID = setTimeout( "timeoutBingCallback()", msecTimeout );
    }
  
    var childContent = getValidElementById("Content");
    if (childContent)
    {
      script.type = "text/javascript";
      script.src = url;
      document.body.insertBefore(script, childContent);
    }
  }
}

/**
 * Builds a Bing API searchBingJSON request from an endpoint URL and parameter dictionary.
 * @param endpoint string
 * @param parameterList associative array of string key value pairs.
 * @return string Bing JSON endpoint query
 */
function buildUrl(endpoint, parametersList)
{
  var flagAddedParameter = false;
  var urlBuilder = new StringBuilder(endpoint);
  for (var parameterKey in parametersList)
  {
    var parameterValue = parametersList[parameterKey];
    if (parameterValue.length == 0)
    {
      continue;
    }
    
    if (flagAddedParameter)
    {
      urlBuilder.append("&");
      flagAddedParameter = false;
    }
    
    /*
     * Quirkiness in Bing API, Sources does not like encoded URI value
     */
    if ( parameterKey.toLowerCase() != "Sources".toLowerCase() )
    {
      parameterValue = encodeURIComponent(parameterValue);
    }
    
    urlBuilder.append(String.format("{0}={1}", parameterKey, parameterValue));
    flagAddedParameter = true;
  }
  
  return urlBuilder.toString();
}

/**
 *  Search callback called by the Bing API JSON block. The results block is rendered from the Bing API response here.
 *
 * @param results JSON response from Bing.
 * @return void
 * @throws Exception if there is a problem with Bing search response.
 */
function searchBingCallback(responseBing)
{
  try
  {
    /*
     * Disable timeout.
     */
    if ( !isNull(gTimeoutBingID) ) 
    {
      clearTimeout( gTimeoutBingID );  
      gTimeoutBingID = null;
    }
    
    if (isNull(responseBing))
    {
      throw new Error("searchBingCallback: Parameter results is null.");
    }
    
    var errorSectionDiv = getValidElementById("errorSection");    
    errorSectionDiv.style.display = "none";
    
    var resultsSectionDiv = getValidElementById("resultsSection");    
    resultsSectionDiv.style.display = "none";
    
    if ( !isNull(responseBing.SearchResponse.Errors) )
    {
      if (isDebugEnabled())
      {
        searchBingErrors(responseBing);
      }
      return;
    }
    else if (isNull(responseBing.SearchResponse.Query))
    {
      return;
    }
    
    setResultsHeader(responseBing.SearchResponse.Query);  
    resultsSectionDiv.style.display = "block";
    searchBingResultListWeb(responseBing);
    searchBingResultImageGallery(responseBing);
  }
  catch (ex)
  {
    debugAlert("searchBingCallback: Exception: "+ex.name+": "+ex.message);
    throw(ex);
  }
}


/**
 * List Web Results from Bing Search
 * 
 * @param responseBing mixed array of Error results
 * @return void
 * @throws Exception if problem with Bing Error response.
 */
function searchBingErrors(responseBing)
{
  try
  {
    var errorSectionDiv = getValidElementById("errorSection");    
    errorSectionDiv.style.display = "block";
    
    var errorListDiv = getValidElementById("errorList");
    clearChildren(errorListDiv);
    
    if (responseBing.SearchResponse.Errors == null || responseBing.SearchResponse.Errors.length == 0)
    {
      errorListDiv.appendChild(document.createTextNode("No Errors."));
      return;
    }
    
    var rE = responseBing.SearchResponse.Errors;
    if (isNull(rE))
    {
      throw new Error("searchBingErrors: Unexpected search response: Errors is null.");
    }
    
    var countErrors = rE.length;
    
    for (var i = 0; i < countErrors; i++)
    {  
      var errorNode = document.createElement("div");
      if (isNull(errorNode))
      {
        throw new Error("searchBingErrors: Element 'errorNode' was not created.");
      }
      
      errorNode.appendChild(createTextBlock('Code: '+rE[i].Code, 'we_esErrorText'));
      errorNode.appendChild(createTextBlock('Message: '+rE[i].Message, 'we_esErrorText'));
      errorNode.appendChild(createLinkBlock(rE[i].HelpUrl, 'Help: '+rE[i].HelpUrl, 'we_esErrorUrl'));
      errorNode.appendChild(createTextBlock('Value: '+rE[i].Value, 'we_esErrorText'));
      errorNode.appendChild(createTextBlock('Parameter: '+rE[i].Parameter, 'we_esErrorText'));
      
      errorListDiv.appendChild(errorNode);
      
      if (i < countErrors - 1)
      {
        errorListDiv.appendChild(createSeparator());
      }
    }
  }
  catch (ex)
  {
    debugAlert("searchBingErrors: Exception: "+ex.name+": "+ex.message);
    throw(ex);
  }
}


/**
 * List Web Results from Bing Search
 *
 * @param responseBing mixed array of Web results
 * @return void
 * @throws Exception if problem with Bing Web response.
 */
function searchBingResultListWeb(responseBing)
{
  try
  {
    var resultListDiv = getValidElementById("resultListWeb");
    clearChildren(resultListDiv);
      
    var navigationDiv = getValidElementById("navigation");
    clearChildren(navigationDiv);
    
    if (responseBing.SearchResponse.Web == null || responseBing.SearchResponse.Web.Results == null || responseBing.SearchResponse.Web.Results.length == 0)
    {
      resultListDiv.appendChild(document.createTextNode("No Web results."));
      return;
    }
    
    var wR = responseBing.SearchResponse.Web.Results;
    if (isNull(wR))
    {
      throw new Error("searchBingResultListWeb: Unexpected search response: Web.Results is null.");
    }
    
    var countResults = wR.length;
    var maxResultsPerPage = 10;
    var totalPages = Math.ceil(countResults / maxResultsPerPage);
    var pageNumber = 1;
    var maxResultsPerPage = 10;
    var countResultsPerPage = 0;
    var pageNode = null;
    var isFirstPage = true;
    
    for (var i = 0; i < countResults; i++)
    {
      if ((i % maxResultsPerPage) == 0) {
        pageDiv = document.createElement("div");
        pageDiv.id = "page"+pageNumber;
        pageDiv.style.display = isFirstPage ? "block" : "none";
        isFirstPage = false;
        resultListDiv.appendChild(pageDiv);
        navigationDiv.appendChild(createLink("javascript: onClick=showPage("+pageNumber+","+totalPages+")", pageNumber + '', "we_srPageTitle" ));
        pageNumber++;
      }
      
      var resultNode = document.createElement("div");
      if (isNull(resultNode))
      {
        throw new Error("searchBingResultListWeb: Element 'resultNode' was not created.");
      }
      
      resultNode.appendChild(createLink(wR[i].Url, wR[i].Title, "we_srResultTitle"));
      resultNode.appendChild(createDescriptionBlock(wR[i].Description, "we_srResultsDesc"));
      resultNode.appendChild(createLink(wR[i].Url, wR[i].DisplayUrl, "we_srResultUrl"));
      
      if (wR[i].CacheUrl != null && wR[i].CacheUrl.length > 0)
      {
        resultNode.appendChild(createLink(wR[i].CacheUrl, "cached page", "we_srCacheUrl"));
      }
      
      pageDiv.appendChild(resultNode);
      
      if (((i % maxResultsPerPage) < maxResultsPerPage - 1) && (i < countResults - 1))
      {
        pageDiv.appendChild(createSeparator());
      }
    }
  }
  catch (ex)
  {
    debugAlert("searchBingResultListWeb: Exception: "+ex.name+": "+ex.message);
    throw(ex);
  }
}


/**
 * List Image Results from Bing Search
 *
 * @param responseBing mixed array of Image results
 * @return void
 * @throws Exception if problem with Bing Image response.
 */
function searchBingResultImageGallery(responseBing)
{
  try
  {
    var resultListDiv = getValidElementById("resultImageGallery");
    clearChildren(resultListDiv);
    
    if (responseBing.SearchResponse.Image == null || responseBing.SearchResponse.Image.Results == null || responseBing.SearchResponse.Image.Results.length == 0)
    {
      resultListDiv.appendChild(document.createTextNode("No Image results."));
      return;
    }
    
    var iR = responseBing.SearchResponse.Image.Results;
    if (isNull(iR))
    {
      throw new Error("searchBingResultImageGallery: Unexpected search response: Image.Results is null.");
    }
    
    for (var i = 0; i < iR.length; i++)
    {
      var imageNode = document.createElement("div");
      imageNode.className = "we_imgFrame";
      resultListDiv.appendChild(imageNode);
      
      var imageThumbnailNode = document.createElement("div");
      imageThumbnailNode.className = "we_imgThumbnail";
      imageNode.appendChild(imageThumbnailNode);
      
      var imageSizeMax = 120; // pixels
      
      var width = iR[i].Width;
      var height = iR[i].Height;
      
      if (width > height)
      {
        height = (height / width) * imageSizeMax;
        width = imageSizeMax;
      } 
      else if (width > height)
      {
        width = (width / height) * imageSizeMax;
        height = imageSizeMax;
      } 
      else
      {
        width = imageSizeMax;
        height = imageSizeMax;
      }
      
      width = width.toFixed(0);
      height = height.toFixed(0);
      
      imageThumbnailNode.appendChild(createImageLink(iR[i].Url, iR[i].Thumbnail.Url, width, height));
      
      var imageFooterNode = document.createElement("div");
      imageFooterNode.className = "we_imgFooter";
      imageNode.appendChild(imageFooterNode);
      
      var varFooter1 = iR[i].Title;
      // varFooter1 = autoEllipseText(imageFooterNode, varFooter1, 120 );
      var imageTitleCharMax = 16; // characters
      if (varFooter1.length > imageTitleCharMax)
      {
        varFooter1 = varFooter1.substring(0, imageTitleCharMax)+"...";
      }
      var varFileSize = iR[i].FileSize/1024;
      var varImageType = iR[i].ContentType.replace( /image\//, '');
      var varFooter2 = String.format("{0} x {1}", iR[i].Width, iR[i].Height);
      var varFooter3 = String.format("{0}k - {1}", varFileSize.toFixed(0), varImageType);
      
      imageFooterNode.appendChild(document.createTextNode(varFooter1));
      imageFooterNode.appendChild(document.createElement("br"));
      imageFooterNode.appendChild(document.createTextNode(varFooter2));
      imageFooterNode.appendChild(document.createElement("br"));
      imageFooterNode.appendChild(document.createTextNode(varFooter3));
      
      imageNode.appendChild(imageThumbnailNode);
      imageNode.appendChild(imageFooterNode);
      resultListDiv.appendChild(imageNode);
    }
  }
  catch (ex)
  {
    debugAlert("searchBingResultImageGallery: Exception: "+ex.name+": "+ex.message);
    throw(ex);
  }
}

/**
 * Clears all children under a node.
 * @param node document element
 * @return void
 */
function clearChildren(node)
{
  if (isNull(node))
  {
    throw new Error("clearChildren: Parameter 'node' is null.");
  }
  
  for (var i = node.childNodes.length - 1; i >= 0; i--)
  {
    node.removeChild(node.childNodes[i]);
  }
}

/**
 * Creates the separator object that exists between two result items.
 *
 * @return void
 */
function createSeparator()
{
  var separator = document.createElement("div");
  if (isNull(separator))
  {
    throw new Error("createSeparator: Element <div> is not created.");
  }
  separator.className = "we_Separator";
  
  return separator;
}

/**
 * Creates the description block with highlights used to display the caption from web results.
 *
 * @param text string Element text
 * @param cssClass string CSS class
 * @return document element <div>
 */
function createDescriptionBlock(text, cssClass)
{
  var div_block = document.createElement("div");
  if (isNull(div_block))
  {
    throw new Error("createDescriptionBlock: Element <div> block is not created.");
  }
  div_block.className = cssClass;
  
  if ((typeof text != 'undefined') && (text.length > 0))
  {
    highlightElementText(div_block, text);
  }
  
  return div_block;
}

/**
 * Creates the description block used to display the caption from web results.
 *
 * @param text string Element text
 * @param cssClass string CSS class
 * @return document element <div>
 */
function createTextBlock(text, cssClass)
{
  var div_block = document.createElement("div");
  if (isNull(div_block))
  {
    throw new Error("createDescriptionBlock: Element <div> block is not created.");
  }
  div_block.className = cssClass;
  
  insertElementInnerText(div_block, text);
  
  return div_block;
}

/**
 * Creates a link element from href, inner text and css class.
 *
 * @param href string link
 * @param text string Element text
 * @param cssClass string CSS class
 * @return document element <a>
 */
function createLinkBlock(href, text, cssClass)
{
  var link_block = document.createElement("a");
  if (isNull(link_block))
  {
    throw new Error("createLink: Element line <a> is not created.");
  }
  
  link_block.href = encodeURI(href);
  link_block.className = cssClass;
  
  insertElementInnerText(link_block, text);
  
  return link_block;
}

/**
 * Creates a link element with highlights from href, inner text and css class.
 *
 * @param href string link
 * @param text string Element text
 * @param cssClass string CSS class
 * @return document element <a>
 */
function createLink(href, text, cssClass)
{
  var link_block = document.createElement("a");
  if (isNull(link_block))
  {
    throw new Error("createLink: Element line <a> is not created.");
  }
  
  link_block.href = encodeURI(href);
  link_block.className = cssClass;
  
  if ((typeof text != 'undefined') && (text.length > 0))
  {
    highlightElementText(link_block, text);
  }
  
  return link_block;
}


/**
 * Creates a link element from href, inner text and css class.
 *
 * @param urlImage string link to image
 * @param urlImage string link to thumbnail image
 * @param width integer width of image 
 * @param height integer height of image
 * @return document element <a>
 */
function createImageLink(urlImage, urlThumbnail, width, height)
{
  var link_block = document.createElement("a");
  link_block.href = encodeURI(urlImage);
  
  var image_block = document.createElement("img");
  image_block.src = encodeURI(urlThumbnail);
  image_block.height = height;
  image_block.width = width;
  
  link_block.appendChild(image_block);
  
  return link_block;
}

/**
 * This function binds the query node from the searchBingJSON response to the results header section.
 *
 * @param queryNode Bing JSON response node.
 * @return void
 */
function setResultsHeader(queryNode)
{
  if (isNull(queryNode))
  {
    throw new Error("setResultsHeader: Parameter 'queryNode' not defined.");
  }
  
  var resultsHeaderDiv = getValidElementById("resultsHeader");
  resultsHeaderDiv.style.display = "block";
  
  var resultSite = getValidElementById("resultSite");
  var resultQuery = getValidElementById("resultQuery");  
  var resultRecourseQuerySection = getValidElementById("resultRecourseQuerySection");
  var resultRecourseQuery = getValidElementById("resultRecourseQuery");

  insertElementInnerText(resultSite, showIncludeDomains());
  
  clearChildren(resultQuery);
  
  if (queryNode.AlteredQuery != null)  
  {
    highlightElementText(resultQuery, removeBingSiteDomains(queryNode.AlteredQuery)+". ");
    resultRecourseQuerySection.style.visibility = "visible";
    
    insertElementInnerText(resultRecourseQuery, removeBingSiteDomains(queryNode.SearchTerms));
    resultRecourseQuery.onclick = function searchBingJSONAltered()
    
    {
      searchBingJSON(removeBingSiteDomains(queryNode.AlterationOverrideQuery));
    };
  }
  else  
  {
    insertElementInnerText(resultQuery, removeBingSiteDomains(queryNode.SearchTerms)+".");
    resultRecourseQuerySection.style.visibility = "hidden";
  }
}

/**
 * Sets the Domain hidden field
 *
 * @return string Domains used in Bing search request.
 */
function showIncludeDomains()
{
  var varBingIncludedDomains = getBingIncludedDomains();
  
  if (!isNull(varBingIncludedDomains) && varBingIncludedDomains.length > 0)
  {
    var varBingIncludedDomains_array = varBingIncludedDomains.split(" ");
    if (varBingIncludedDomains_array.lenght > 1)
    {
      var varBingIncludedDomainsJoin = varBingIncludedDomains_array.join(" | ");
      return String.format("sites: ({0})", varBingIncludedDomainsJoin);
    }
    
    return String.format("site: {0}", varBingIncludedDomains);
  }
  
  return "Web";
}

/**
 * The function strips out the site:
 * {domain} section from the query to improve the look and feel of the site.
 *
 * @param query string Bing search request.
 * @return string Bing search request without domains.
 */
function removeBingSiteDomains(query)
{
  var strBingSiteDomains = getBingSiteDomains();
  return query.replace(strBingSiteDomains, "").trim();
}

/**
 * Gets site domains prepared for Bing search query.
 * Domains must be prefixed with 'site:'.
 *
 * @return string Bing Site Domains from hidden value.
 */
function getBingSiteDomains()
{
  return getValueByElementId('BingSiteDomains');
}

/**
 * Get Bing connection timeout.
 *
 * @return string Bing Included Domains from hidden value.
 */
function getBingConnectTimeout()
{
  return getValueByElementId('Custom404BingConnectTimeout');
}

/**
 * Get the domains that were included for this search.
 *
 * @return string Bing Included Domains from hidden value.
 */
function getBingIncludedDomains()
{
  return getValueByElementId('BingIncludedDomains');
}


/**
 * Gets the searchBingJSON domain from the Domain hidden field.
 */
function getBingSearchTerms()
{
  return getValueByElementId('BingSearchTerms');
}

/**
 * Show no results.
 * 
 * @return void
 */
function showNoResults(strMessage)
{
  try
  {   
    var resultsSectionDiv = getValidElementById("resultsSection");
    resultsSection.style.display = "block";
    
    var resultListWebDiv = getValidElementById("resultListWeb");    
    clearChildren(resultListWebDiv);
      
    resultListWebDiv.appendChild(document.createTextNode(strMessage));
    resultListWebDiv.className = "we_srNoResults";
  }
  catch (ex)  
  {
    debugAlert("showNoResults: Exception: "+ex.name+": "+ex.message);
    throw ex;
  }
}

/**
 * Prepare Bing search URL from Bing Assets' url.
 * 
 * @return void
 */
function setBingSearchUrl()
{
  var BingSearchUrlElement = getValidElementById("BingSearchUrl");
  var BingSearchUrl = getValidElementById("BingSearchAssetUrlEndpoint").value;
  
  BingSearchUrlElement.href = BingSearchUrl;
}

/**
 * Prepare search logo from Bing Assets' url.
 * 
 * @return void
 */
function setBingSearchLogo()
{
  var BingSearchLogoElement = getValidElementById("BingSearchLogo");
  var BingSearchLogo = getValidElementById("BingSearchAssetLogoBigDark").value;
  
  BingSearchLogoElement.src = BingSearchLogo;
}

/**
 * Prepare search icon from Bing Assets' url.
 * 
 * @return void
 */
function setDoSearchIcon()
{
  var DoSearchElement = getValidElementById("DoSearch");
  var DoSearchIcon = getValidElementById("BingSearchAssetIcon24").value;
  
  DoSearchElement.src = DoSearchIcon;
}

/**
 * Determine if 'what' is either undefined or null.
 *
 * @param element Document element type 
 * @return boolean
 */
function isNull(element)
{
  return element==null || (typeof element == 'undefined');
}

/**
 * Insert text between open/close tags.
 * IE and FireFox use different DOM commands to do this action.
 * 
 * @param element Document element
 * @param text string Insert within provided Document element.
 * @return void
 */
function insertElementInnerText(element, text)
{
  try
  {
    if ((typeof text == 'undefined') || isNull(text) || (text.length == 0))    
    {
      text = '';
    }
    
    if (isNull(element))    
    {
      throw new Error("insertElementInnerText: Parameter 'element' is not defined.");
    }
    
    if(typeof element.textContent != 'undefined')    
    {
      element.textContent = text;
    }
    else if(typeof element.innerText != 'undefined')
    {
      element.innerText = text;
    }
    else    
    {
      throw new Error("insertElementInnerText: neither 'innerText' nor 'textContent' are available.");
    }
  }
  catch (ex)
  {
    debugAlert("insertElementInnerText: Exception: "+ex.name+": "+ex.message);
    throw(ex);
  }
}

/**
 * Add <strong> tags if Bing entered Highlighting characters are found.
 * Highlighting characters are \uE000 for <strong> and \uE001 for </strong>.
 * 
 * @param element Document element
 * @param text string Insert within Document element with highlights.
 * @return void
 */
function highlightElementText(element, text)
{
  if (isNull(element))  
  {
    throw new Error("highlightElementText: Parameter 'element' is not defined.");
  }
  
  if ((typeof text == 'undefined') || isNull(text) || (text.length == 0))  
  {
    return;
  }
  
  var indexStart = 0;
  var indexEnd = 0;
  
  while ((indexStart = text.indexOf("\uE000")) >= 0)  
  {
    indexEnd = text.indexOf("\uE001");
    var pre_text = text.substring(0, indexStart);
    if (pre_text.length > 0)
    {
      var node_text = pre_text;
      element.appendChild(document.createTextNode(node_text));
    }
    
    var strong_block = document.createElement("strong");
    element.appendChild(strong_block);
    var inner_text = text.substring(indexStart+1, indexEnd);
    insertElementInnerText(strong_block, inner_text);
    
    if (indexEnd + 1 != text.length)
    {
      text = text.substring(indexEnd + 1);
      if (text.length == 0)
      {
        break;
      }
    }
    else
    {
      text = "";
      break;
    }
  }
  
  if (text.length > 0)   
  {
    element.appendChild(document.createTextNode(text));
  }
}

/**
 * Add <strong> tags if Bing entered Highlighting characters are found.
 * Highlighting characters are uE000 for <strong> and uE001 for </strong>.
 *
 * @param text string
 * @return string Provided text with highlights.
 */
function highlightText(text)
{
  if ((typeof text == 'undefined') || isNull(text) || (text.length == 0))
  {
    return;
  }
  
  text = text.replace( /\uE000/, "<strong>" );
  text = text.replace( /\uE001/, "</strong>" );
  
  return text;
}

/**
 * Is Custom404DebugEnabled set to Enable?
 *
 * @return boolean.
 */
function isDebugEnabled()
{
  var eCustom404DebugEnabled = getValidElementById("Custom404DebugEnabled", false);
  return !isNull(eCustom404DebugEnabled) && eCustom404DebugEnabled.value;
}

/**
 * Localized Alert debug
 *
 * @param strMessage string Alert message
 * @return void
 */
function debugAlert(strMessage)
{
  var eCustom404DebugEnabled = getValidElementById("Custom404DebugEnabled", false);
  if( isDebugEnabled() )
  {
    alert(strMessage);
  }
}

/**
 * Simple pagination function
 *
 * @param pageId integer Page number in pagination.
 * @param pageCount integer Total number of pages to paginate.
 * @return void
 */
function showPage(pageId, pageCount)
{
  for ( var i = 0; i <= pageCount; i++ )
  {
    if (document.getElementById('page'+i))
    {
      document.getElementById('page'+i).style.display='none';
    }
  }
  
  if (document.getElementById('page'+pageId))
  {
    document.getElementById('page'+pageId).style.display='block';
  }
}